#include "widgetpainsimple.h"
#include "ui_widgetpainsimple.h"
#include <QDebug>
#include <QMessageBox>

WidgetPAInSimple::WidgetPAInSimple(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::WidgetPAInSimple)
{
    ui->setupUi(this);
    //指针初始化为空
    m_streamIn = NULL;
    //先初始化PA库
    Pa_Initialize();

    //调用初始化界面函数
    initUI();
}

WidgetPAInSimple::~WidgetPAInSimple()
{
    //关闭文件和套接字
    m_file.close();
    m_socket.close();
    //指针置空
    m_streamIn = NULL;
    //终止 PA 库引用
    Pa_Terminate();
    //销毁窗口
    delete ui;
}

//初始化界面
void WidgetPAInSimple::initUI()
{
    //声道数量
    QStringList listChannels;
    listChannels<<"1"<<"2";
    ui->comboBoxChannels->addItems( listChannels );
    //采样位宽，这个示例约定8位无符号整数，16位有符号整数和32位浮点数
    QStringList listSampleSizes;
    listSampleSizes<<"8"<<"16"<<"32";
    ui->comboBoxSampleSize->addItems( listSampleSizes );
    //设置默认位宽为 16，PA库的8位采样有bug，建议16位或以上位宽采样
    ui->comboBoxSampleSize->setCurrentText( "16" );
    //采样频率，示例枚举比较通用的三个
    QStringList listSampleRates;
    listSampleRates<<"8000"<<"44100"<<"48000";
    ui->comboBoxSampleRate->addItems( listSampleRates );
    //时间片帧数范围，8k采样一毫秒8帧，48k采样，一秒钟48000帧
    ui->spinBoxFramesPerBuffer->setRange( 8, 48000 );
    //默认时间片 20 毫秒，8k采样就是 160 帧
    ui->spinBoxFramesPerBuffer->setValue( 160 );

    //默认IP地址
    ui->lineEditIP->setText( "127.0.0.1" );
    //端口号范围
    ui->spinBoxPort->setRange( 1, 65535 );
    ui->spinBoxPort->setValue( 12345 );//默认端口
    //默认文件名
    ui->lineEditFile->setText( "E:/pasimple_8k1c16bit.pcm" );
    //界面设置完毕
    return;
}

//先编写回调函数，用户数据保存窗口类对象的this指针
static int audioInCallback( const void *inputBuffer,
                     void *outputBuffer,
                     unsigned long frameCount,
                     const PaStreamCallbackTimeInfo* timeInfo,
                     PaStreamCallbackFlags statusFlags,
                     void *userData )
{
    //采集的数据，文件和网络对采样点类型不关心，只需要知道数据的总字节数
    const char* dataIn = (const char*)inputBuffer;
    //窗口指针
    WidgetPAInSimple *pWnd = (WidgetPAInSimple *)userData;
    //qint64 nsBegin = pWnd->m_calcTime.nsecsElapsed(); //本次回调函数开始执行时间
    //计算数据字节数
    int nChannels = pWnd->m_nChannels;//通道数量
    int nBytesPerSample = (pWnd->m_nSampleSize) / 8; //样本字节数
    int nAllBytes = frameCount*nChannels*nBytesPerSample;
    //写入文件
    (pWnd->m_file).write( dataIn, nAllBytes );
    //写入套接字
    (pWnd->m_socket).write( dataIn, nAllBytes );
    //调用状态显示函数
    pWnd->showStatus(frameCount, nAllBytes);
    //qint64 nsEnd = pWnd->m_calcTime.nsecsElapsed(); //本次回调函数结束时间
    //qDebug()<<"one call ns: "<<nsEnd-nsBegin;
    //回调函数一定要有返回值
    return paContinue; //继续采集
}

//设置状态字符串
void WidgetPAInSimple::showStatus(int nFrames, int nBytes)
{
    QString strStatus;
    qint64 msCount = m_calcTime.elapsed();//毫秒数
    //构造状态字符串
    strStatus = tr("%1.%2 s, %3 frames, %4 bytes")
            .arg( msCount / 1000 )
            .arg(msCount % 1000, 3, 10, QChar('0'))
            .arg( nFrames )
            .arg( nBytes );
    ui->labelStatus->setText( strStatus );//显示到状态条
    return;
}

//根据采样位宽获取采样点数据格式
PaSampleFormat WidgetPAInSimple::getSampleFormat(int sampleSize)
{
    switch (sampleSize) {
    case 8:
        return paUInt8; //8位无符号整数
        break;
    case 16:
        return paInt16; //16位有符号整数
        break;
    case 32:
        return paFloat32; //32位浮点数
        break;
    default:
        return paInt16;//16位有符号整数
        break;
    }
}

void WidgetPAInSimple::on_pushButtonStart_clicked()
{
    //尝试打开文件
    m_file.setFileName( ui->lineEditFile->text() );
    if( ! m_file.open( QIODevice::WriteOnly ) )
    {
        QMessageBox::warning(this, tr("打开文件"), tr("打开文件失败，请检查文件名和权限。"));
        return;
    }
    //尝试转换字符串为IP地址对象
    QHostAddress ipAddr;
    if( ! ipAddr.setAddress( ui->lineEditIP->text() ) )
    {
        QMessageBox::warning(this, tr("IP地址"), tr("IP地址不合法。"));
        m_file.close(); //文件释放
        return;
    }
    quint16 nPort = ui->spinBoxPort->value();
    //设置UDP套接字的目标地址和端口
    m_socket.connectToHost(ipAddr, nPort);

    //获取录音参数
    m_nChannels = ui->comboBoxChannels->currentText().toInt();
    m_nSampleSize = ui->comboBoxSampleSize->currentText().toInt();
    m_nSampleRate = ui->comboBoxSampleRate->currentText().toInt();
    m_nFramesPerBuffer = ui->spinBoxFramesPerBuffer->value();

    //打开流
    PaError err = Pa_OpenDefaultStream(
                &m_streamIn,
                m_nChannels,
                0,
                getSampleFormat( m_nSampleSize ),
                m_nSampleRate,
                m_nFramesPerBuffer,
                audioInCallback, //回调函数
                (void*)this      //窗口对象指针
                );
    QString strErr;
    //判断是否有错
    if( paNoError != err )
    {
        //显示错误消息
        strErr = tr("PortAudio error: %1").arg( Pa_GetErrorText( err ) );
        QMessageBox::warning(this, tr("Pa_OpenDefaultStream"), strErr);
        m_file.close();
        return;
    }
    //开始流
    err = Pa_StartStream( m_streamIn );
    if( paNoError != err )
    {
        //显示错误消息
        strErr = tr("PortAudio error: %1").arg( Pa_GetErrorText( err ) );
        QMessageBox::warning(this, tr("Pa_StartStream"), strErr);
        m_file.close();
        return;
    }
    m_calcTime.start(); //计时开始
    //禁用输入控件，固定参数
    holdParas(true);
}

//采集时固定参数
void WidgetPAInSimple::holdParas(bool bHold)
{
    ui->comboBoxChannels->setDisabled( bHold );
    ui->comboBoxSampleSize->setDisabled( bHold );
    ui->comboBoxSampleRate->setDisabled( bHold );
    ui->lineEditIP->setDisabled( bHold );
    ui->lineEditFile->setDisabled( bHold );
    ui->spinBoxFramesPerBuffer->setDisabled( bHold );
    ui->spinBoxPort->setDisabled( bHold );
    ui->pushButtonStart->setDisabled( bHold );
}

void WidgetPAInSimple::on_pushButtonStop_clicked()
{
    //判断流是否在工作
    if( Pa_IsStreamActive(m_streamIn) <=0 )
    {
        //流没有开始或者处于错误状态，不需要停止
        return;
    }

    PaError err;
    qint64 msCount = m_calcTime.elapsed();//结束时间毫秒数
    //流正在工作，停止流
    err = Pa_StopStream( m_streamIn );
    err = Pa_CloseStream( m_streamIn );
    if( paNoError != err )
    {
        //显示错误消息
        QString strErr = tr("PortAudio error: %1").arg( Pa_GetErrorText( err ) );
        QMessageBox::warning(this, tr("Pa_CloseStream"), strErr);
    }
    //关闭文件
    m_file.close();
    m_socket.disconnectFromHost(); //断开
    m_streamIn = NULL; //指针置空
    //恢复控件
    holdParas(false);
    QString strStatus = tr("采集结束，耗时：%1.%2 s")
            .arg( msCount / 1000 )
            .arg(msCount % 1000, 3, 10, QChar('0'));
    //显示状态
    ui->labelStatus->setText( strStatus );
}
